package SquarePackage;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;

import Border.Border;
import Border.Door;
import Border.EmptySpace;
import Border.Wall;
/**
 * this class represents the parent class of all classes which support the teleport function
 	It also handles all the appropriate methods needed for teleporting.
 	This class will be made with Nominal Programming.
 * @author Florian en Brecht
 * @version 1.1
 *
 */
public abstract class TeleportableSquare extends Square{
	
	//*************************
	//	CONSTRUCTOR
	//*************************
	 public TeleportableSquare() {
		super();
	}
	//******************
    // TELEPORTATION
	//******************
	 /**
	  * This method is used in the constructor to preset the Borders HashMap
	  */
	 protected  void preSetBorders(){
		 borders = new EnumMap<Direction, Border>(Direction.class);
		 for (Direction direction : Direction.values()){
			 EmptySpace newEmpty = new EmptySpace(this,direction);    
	        borders.put(direction,newEmpty);
	       	newEmpty.setSquare(this);
		 }
		}
	 /**
	  * List of squares which form a destination for this teleportable square, these squares are teleportable squares to,
	  * which means that they could have the teleport function too, but keep in mind that this is not always so.
	  */
	Set<TeleportableSquare> destinations = new HashSet<TeleportableSquare>();
	/**
	 * Adds a destination square to the possible destinations where we want to teleport too.
	 * @pre The given destination must be an effective square.
	 * 		|destination!=null
	 * @pre The given destination is not terminated
	 *		|detination.isTerminated()== false
	 * @param destination
	 * 				the square to be added
	 * @post The size of the set is incremented and
	 * 		the destination square can now be teleported too.
	 * 		| destinations.canTeleportTo(destination)
	 * 		| (new)destinations.size() = (this) destinations.size()+1
	 */
	public void addDestination(TeleportableSquare destination){
		assert(destination!=null && !destination.isTerminated());
		destinations.add(destination);
	}
	/**
	 * Method to check if it is possible to teleport here.
	 * @param	destination
	 * 			the square to be checked.
	 */
	@Basic
	public boolean canTeleportTo(TeleportableSquare destination){
		return destinations.contains(destination);
	}
	/**
	 * method which chooses the square we are teleporting to randomly from our destination set
	 * @return  TeleportableSquare
	 * 			A square to which we can telport too.
	 * 			|canTeleportTo(result)==true;
	 */
	public TeleportableSquare teleport(){
		Random random = new Random();
		TeleportableSquare[] destinationsArray = (TeleportableSquare[]) destinations.toArray();
		return destinationsArray[random.nextInt(destinations.size())];
	}
	/**
	 * method to check if the teleportation function of this square is indeed activated
	 * @return 
	 * @return	Boolean
	 * 			this is true if the destinations set is not empty.
	 * 	 		| if(destinations.size()>=1)(result==true)
	 */
	@Basic
	public boolean hasTeleportationEnabled(){
		return destinations.size()>=1;
	}
	//******************
	//	HUMIDITY
	//******************
	/**
	 * Set the humidity of the square to a given value.
	 * 
	 * @pre		the humidity  to be a valid percentage between 0 and 100
	 * 			|isValidHumidity(humidity)
	 * @param	humidity
	 * 			the new humidity for this square
	 * @post	the humidity of the square is set to the given humidity
	 * 			|new.getHumidity=humidity
	 */
	@Raw 
	public void setHumidity(BigDecimal humidity){
		assert isValidHumidity(humidity);
		this.humidity = humidity.setScale(2,BigDecimal.ROUND_HALF_UP);
	}
	//***********************
	// 	BORDERS
	//***********************
	/**
	 * A method to set a border in one of the six possible directions
	 * @param 	direction
	 * 			the direction to set the border
	 * @param	border
	 * 			the type of border to set
	 * @post 	if and only if the arguments are both valid, the square will
	 *       	have a bordertype with the same value as border in the direction of 
	 *       	specified by direction, if it  a different value this  value is 
	 *       	replaced by the new value.
	 *       	In case there is already a border, with the same border type,
	 *       	 in this direction nothing changes .
	 *       	| if(direction!=null && border!=null )
	 *       	| new.Border(direction,border) == true
	 * @post	if the border is not effective, nothing changes
	 *  	    | if(directions == null)
	 *	 	    |       then new.getBorders() == this.getBorders()       
	 * @throws	IllegalArgumentException if direction or border is null
	 * 			| direction == null
	 * 			| border == null
	 */
	public void setBorder(Direction direction, Border border)throws IllegalArgumentException{
		if(!isValidDirection(direction)){
			throw new IllegalArgumentException("This direction does not exist");
		}
		if(!canHaveAsBorder(border, direction)) {
			throw new IllegalArgumentException("illegal border type");
		}
		Map<Direction,Border> tempBorders = getBorders().clone();
		tempBorders.put(direction, border);
		
		
		if(hasProperBorders(tempBorders)){
			borders.put(direction, border);
			border.setSquare(this);
		}
		else{ throw new IllegalArgumentException("these are not valid borders");}
	}

	//*************************
	//	TEMPERATURE
	//*************************
	/**
	 * Mutator to set the temperature, explicitly for Celcius
	 * 
	 * @param	temperature
	 * 			the new temperature given to the square
	 * @throws 	IllegalArgumentException
	 * 			caused by the use of previous methods
	 * @throws 	IllegalArgumentException
	 * 			if and only if the temperature scale is null.
	 * 			| if(scale=null) throw IllegalArgumentException
	 */
	@Raw
	public void setTemperature(double temperature) throws IllegalArgumentException{
		setTemperature(temperature,TemperatureScale.CELSIUS);
	}
	/**
	 * Mutator to set the temperature with a given scale
	 * 
	 * @param	temperature
	 * 			the new temperature given to the square
	 * @param 	scale
	 * 			the scale the temperature is specified in
	 * @throws 	IllegalArgumentException
	 * 			caused by the use of previous methods
	 * @throws 	IllegalArgumentException
	 * 			if and only if the temperature scale is null.
	 * 			| if(scale=null) throw IllegalArgumentException
	 */
    public void setTemperature(double temperature, TemperatureScale scale)throws IllegalArgumentException 
    {
    	if (! isValidScale(scale)){
			throw new IllegalArgumentException("This is not a valid scale");
		}
    	if (!isValidTemperature(temperature,scale)) {
			throw new IllegalArgumentException("the temperature is not valid," +
					" because it is not in the proper domain between" + 
					getMinimumTemp(scale)+ scale.getSign() + "and" + getMaximumTemp(scale) + scale.getSign());
		}
    this.temperature.setTemperature(temperature,scale);
    }
	/**
	 * checker for a valid scale
	 * @param	scale
	 * 			the scale to check
	 * @return	True if and only if the scale is not null
	 * 			| if(scale!=null)
	 * 			| then result=true
	 */
}
